package org.luxor.commons.workflow.component.cmd;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.activiti.bpmn.model.Activity;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.impl.bpmn.behavior.BpmnActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.activiti.engine.impl.cmd.NeedsActiveTaskCmd;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.util.ProcessDefinitionUtil;
import org.luxor.commons.workflow.constant.TransientVariableConst;
import org.luxor.commons.workflow.constant.VariableConst;

import java.util.ArrayList;
import java.util.List;


/**
 * 多实例任务-减签
 *
 * @author Mr.yan  @date 2022/2/23
 */
public class DeleteMultiInstanceExecutionCmd extends NeedsActiveTaskCmd<Void> {
    private static final long serialVersionUID = 1L;
    private final BpmnActivityBehavior bpmnActivityBehavior = new BpmnActivityBehavior();

    private final String collectionElementIndexVariable = "loopCounter";
    private final String NUMBER_OF_INSTANCES = "nrOfInstances";
    private final String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";
    private final String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";

    private final boolean executionIsCompleted;

    private ExecutionEntity rootExecution;
    private ExecutionEntity miExecution;
    private Activity miActivityElement;
    private MultiInstanceLoopCharacteristics loopCharacteristics;

    public DeleteMultiInstanceExecutionCmd(String taskId, boolean executionIsCompleted) {
        super(taskId);
        this.executionIsCompleted = executionIsCompleted;
    }

    @Override
    public Void execute(CommandContext commandContext, TaskEntity task) {
        this.miExecution = task.getExecution();
        if (this.miExecution == null) {
            throw new ActivitiObjectNotFoundException("找不到流程执行实例");
        }
        this.rootExecution = getMultiInstanceRootExecution(miExecution);
        if (rootExecution == null) {
            throw new ActivitiObjectNotFoundException("找不到父级流程执行实例");
        }

        // 是多实例会签？
        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(miExecution.getProcessDefinitionId());
        this.miActivityElement = (Activity) bpmnModel.getFlowElement(miExecution.getCurrentFlowElement().getId());
        if (!(this.miActivityElement.getBehavior() instanceof MultiInstanceActivityBehavior)) {
            throw new ActivitiObjectNotFoundException("无法执行，任务不属于多实例活动节点");
        }
        this.loopCharacteristics = miActivityElement.getLoopCharacteristics();
        if (this.loopCharacteristics == null) {
            throw new ActivitiObjectNotFoundException("无法执行，没有定义循环特征(multiInstanceLoopCharacteristics)");
        }

        // 删除并行会签
        if (miActivityElement.getBehavior() instanceof ParallelMultiInstanceBehavior) {
            deleteParallelMultiInstanceExecution(commandContext, task);
            return null;
        }

        // 删除顺序会签
        if (miActivityElement.getBehavior() instanceof SequentialMultiInstanceBehavior) {
            deleteSequentialMultiInstanceExecution(commandContext, task);
            return null;
        }
        return null;
    }

    private void deleteParallelMultiInstanceExecution(CommandContext commandContext, TaskEntity task) {
        // 停用当前实例
        miExecution.inactivate();
        // 强制更新
        lockFirstParentScope(miExecution);

        // 设置会签参数
        if (executionIsCompleted) {
            Integer nrOfCompletedInstances = getLoopVariable(rootExecution, NUMBER_OF_COMPLETED_INSTANCES);
            setLoopVariable(rootExecution, NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances + 1);
            Integer nrOfActiveInstances = getLoopVariable(rootExecution, NUMBER_OF_ACTIVE_INSTANCES);
            setLoopVariable(rootExecution, NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances - 1);
            Integer loopCounter = getLoopVariable(miExecution, collectionElementIndexVariable);
            setLoopVariable(miExecution, collectionElementIndexVariable, loopCounter + 1);
        } else {
            Integer nrOfInstances = getLoopVariable(rootExecution, NUMBER_OF_INSTANCES);
            setLoopVariable(rootExecution, NUMBER_OF_INSTANCES, nrOfInstances - 1);
            Integer nrOfActiveInstances = getLoopVariable(rootExecution, NUMBER_OF_ACTIVE_INSTANCES);
            setLoopVariable(rootExecution, NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances - 1);
        }

        Integer nrOfCompletedInstances = getLoopVariable(rootExecution, NUMBER_OF_COMPLETED_INSTANCES);
        Integer nrOfInstances = getLoopVariable(rootExecution, NUMBER_OF_INSTANCES);

        // 触发实例运转
        if (nrOfCompletedInstances >= nrOfInstances) {
            commandContext.getExecutionEntityManager().deleteChildExecutions(rootExecution, "delete multi instance", false);
            removeLocalLoopVariable(rootExecution, collectionElementIndexVariable);
            rootExecution.setScope(false);
            rootExecution.setMultiInstanceRoot(false);
            commandContext.getAgenda().planTakeOutgoingSequenceFlowsOperation(rootExecution, true);
        } else {
            commandContext.getExecutionEntityManager().deleteExecutionAndRelatedData(miExecution, "delete multi instance", false);
            commandContext.getAgenda().planContinueProcessInCompensation(miExecution);
        }
    }

    private void deleteSequentialMultiInstanceExecution(CommandContext commandContext, TaskEntity task) {
        // 停用当前实例
        miExecution.inactivate();
        // 强制更新
        lockFirstParentScope(miExecution);
        // 通知节点结束
        Context.getCommandContext().getHistoryManager().recordActivityEnd(miExecution, "delete multi instance");
        // 发送节点结束事件
        callActivityEndListeners(miExecution);

        // 设置会签参数
        if (executionIsCompleted) {
            Integer nrOfCompletedInstances = getLoopVariable(rootExecution, NUMBER_OF_COMPLETED_INSTANCES);
            setLoopVariable(rootExecution, NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances + 1);
        } else {
            Integer nrOfInstances = getLoopVariable(rootExecution, NUMBER_OF_INSTANCES);
            setLoopVariable(rootExecution, NUMBER_OF_INSTANCES, nrOfInstances - 1);
        }

        Integer nrOfCompletedInstances = getLoopVariable(rootExecution, NUMBER_OF_COMPLETED_INSTANCES);
        Integer nrOfInstances = getLoopVariable(rootExecution, NUMBER_OF_INSTANCES);
        if (nrOfCompletedInstances >= nrOfInstances) {
            rootExecution.setScope(false);
            rootExecution.setMultiInstanceRoot(false);
            rootExecution.setCurrentFlowElement(miExecution.getCurrentFlowElement());

            setLoopVariable(rootExecution, NUMBER_OF_ACTIVE_INSTANCES, 0);
            commandContext.getExecutionEntityManager().deleteChildExecutions(rootExecution, "MI_END", false);

            bpmnActivityBehavior.performDefaultOutgoingBehavior(rootExecution);
        } else {
            ExecutionEntity childExecution = Context.getCommandContext().getExecutionEntityManager()
                    .createChildExecution(rootExecution);
            childExecution.setCurrentFlowElement(rootExecution.getCurrentFlowElement());
            rootExecution.setMultiInstanceRoot(true);
            rootExecution.setActive(false);

            ArrayNode arrayNodes = (ArrayNode) rootExecution.getVariable(VariableConst.ASSIGNEE_LIST);
            List<String> assigneeList = new ArrayList<>();
            for (JsonNode node : arrayNodes) {
                assigneeList.add(node.asText());
            }
            setVariable(childExecution, VariableConst.ASSIGNEE_LIST, assigneeList);
            Integer loopCounter = getLoopVariable(miExecution, collectionElementIndexVariable) + 1;
            setLoopVariable(childExecution, TransientVariableConst.ASSIGNEE, assigneeList.get(loopCounter));
            setLoopVariable(childExecution, collectionElementIndexVariable, loopCounter);

            setLoopVariable(rootExecution, NUMBER_OF_ACTIVE_INSTANCES, 1);
            commandContext.getExecutionEntityManager().deleteExecutionAndRelatedData(miExecution, "delete multi instance", false);

            commandContext.getAgenda().planContinueProcessInCompensation(childExecution);
        }
    }

    private Integer getLoopVariable(DelegateExecution execution, String variableName) {
        Object value = execution.getVariableLocal(variableName);
        DelegateExecution parent = execution.getParent();
        while (value == null && parent != null) {
            value = parent.getVariableLocal(variableName);
            parent = execution.getParent();
        }
        return value == null ? 0 : (Integer) value;
    }

    private void setLoopVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setVariableLocal(variableName, value);
    }

    private void removeLocalLoopVariable(DelegateExecution execution, String variableName) {
        execution.removeVariableLocal(variableName);
    }

    private void setVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setVariable(variableName, value);
    }

    private void setTransientVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setTransientVariable(variableName, value);
    }

    private ExecutionEntity getMultiInstanceRootExecution(ExecutionEntity executionEntity) {
        ExecutionEntity multiInstanceRootExecution = null;
        ExecutionEntity currentExecution = executionEntity;
        while (currentExecution != null && multiInstanceRootExecution == null && currentExecution.getParent() != null) {
            if (currentExecution.isMultiInstanceRoot()) {
                multiInstanceRootExecution = currentExecution;
            } else {
                currentExecution = currentExecution.getParent();
            }
        }
        return multiInstanceRootExecution;
    }


    private void lockFirstParentScope(DelegateExecution execution) {
        ExecutionEntityManager executionEntityManager = Context.getCommandContext().getExecutionEntityManager();
        boolean found = false;
        ExecutionEntity parentScopeExecution = null;
        ExecutionEntity currentExecution = (ExecutionEntity) execution;
        while (!found && currentExecution != null && currentExecution.getParentId() != null) {
            parentScopeExecution = executionEntityManager.findById(currentExecution.getParentId());
            if (parentScopeExecution != null && parentScopeExecution.isScope()) {
                found = true;
            }
            currentExecution = parentScopeExecution;
        }
        parentScopeExecution.forceUpdate();
    }

    /**
     * Since no transitions are followed when leaving the inner activity, it is needed to call the end listeners yourself.
     */
    private void callActivityEndListeners(DelegateExecution execution) {
        Context.getCommandContext().getProcessEngineConfiguration().getListenerNotificationHelper()
                .executeExecutionListeners(this.miActivityElement, execution, ExecutionListener.EVENTNAME_END);
    }
}
